/**
 * trzsz: https://github.com/trzsz/trzsz.js
 * Copyright(c) 2023 Lonny Wong <lonnywong@qq.com>
 * @license MIT
 */

import { Terminal, type IDisposable, type ITerminalAddon } from "xterm";
import { type TrzszOptions, TrzszFilter } from "trzsz";

function removeLastChar(str: string) {
  if (str.length === 0) {
    return ""; // 如果字符串已经为空，直接返回空字符串
  }
  return str.substring(0, str.length - 1);
}

/**
 * An addon for xterm.js that supports trzsz
 */
export class TrzszAddon implements ITerminalAddon {
  private socket: WebSocket;
  private disposables: IDisposable[] = [];
  private options: TrzszOptions;
  private trzsz: TrzszFilter | null = null;
  private command: string = "";

  /**
   * Create a TrzszAddon
   * @param {WebSocket} socket - The websocket connection.
   * @param {TrzszOptions} options - The trzsz options.
   */
  constructor(socket: WebSocket, options?: TrzszOptions) {
    this.socket = socket;
    this.options = options || {};
    // always set binary type to arraybuffer
    this.socket.binaryType = "arraybuffer";
  }

  /**
   * Activate TrzszAddon
   * @param {Terminal} terminal - The xterm.js terminal
   */
  public activate(terminal: Terminal): void {
    const writeToTerminal = (data: string | ArrayBuffer) => {
      terminal.write(typeof data === "string" ? data : new Uint8Array(data));
    };
    const sendToServer = (data: string | Uint8Array) => {
      if (this.socket.readyState !== 1) {
        return;
      }
      if (typeof data === "string") {
        this.socket.send(
          JSON.stringify({
            type: 1,
            content: data
          })
        );
      } else {
        this.socket.send(data);
      }
    };
    this.trzsz = new TrzszFilter({
      writeToTerminal: (this.options.writeToTerminal || writeToTerminal) as any,
      sendToServer: this.options.sendToServer || sendToServer,
      chooseSendFiles: this.options.chooseSendFiles,
      chooseSaveDirectory: this.options.chooseSaveDirectory,
      terminalColumns: terminal.cols,
      isWindowsShell: this.options.isWindowsShell,
      dragInitTimeout: this.options.dragInitTimeout
    });

    // 拖动上传
    terminal.element?.addEventListener("dragover", (event) => event.preventDefault());
    terminal.element?.addEventListener("drop", (event) => {
      event.preventDefault();
      if (event.dataTransfer !== null && this.trzsz !== null) {
        this.trzsz
          .uploadFiles(event.dataTransfer.items)
          .then(() => console.log("upload success"))
          .catch((err) => console.log(err));
      }
    });

    this.disposables.push(
      this.addSocketListener(this.socket, "message", (ev) =>
        this.trzsz?.processServerOutput(ev.data)
      )
    );

    this.disposables.push(
      terminal.onData((data) => {
        if (data.charCodeAt(0) === 127) {
          // 退格, 在这里执行你想要的操作
          this.command = removeLastChar(this.command);
        } else if (data.charCodeAt(0) == 13) {
          // 回车
          // 如果输入的是clear命令，则前端的终端也进行清理
          if (this.command.trim() === "clear") {
            console.log("clear terminal");
            terminal.clear();
          }
          this.command = "";
        } else {
          // 非特殊指令追加命令
          this.command += data;
        }
        this.trzsz?.processTerminalInput(data);
      })
    );
    this.disposables.push(terminal.onBinary((data) => this.trzsz?.processBinaryInput(data)));
    this.disposables.push(terminal.onResize((size) => this.trzsz?.setTerminalColumns(size.cols)));
    this.disposables.push(this.addSocketListener(this.socket, "close", () => this.dispose()));
    this.disposables.push(this.addSocketListener(this.socket, "error", () => this.dispose()));
  }

  /**
   * Dispose TrzszAddon
   */
  public dispose(): void {
    for (const d of this.disposables) {
      d.dispose();
    }
    this.trzsz = null;
  }

  /**
   * Upload files or directories to the server.
   * @param {string[] | DataTransferItemList} items - The files or directories to upload.
   */
  public async uploadFiles(items: string[] | DataTransferItemList) {
    if (this.trzsz) {
      return this.trzsz.uploadFiles(items);
    } else {
      throw new Error("Addon has not been activated");
    }
  }

  /**
   * Add websocket event handler
   * @param {WebSocket} socket - The websocket connection.
   * @param {K} type - The websocket event type.
   * @param {Function} handler - The websocket event handler.
   * @return {IDisposable} The disposable object.
   */
  protected addSocketListener<K extends keyof WebSocketEventMap>(
    socket: WebSocket,
    type: K,
    handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any
  ): IDisposable {
    socket.addEventListener(type, handler);
    return {
      dispose: () => {
        if (!handler) {
          return; // already disposed
        }
        socket.removeEventListener(type, handler);
      }
    };
  }
}
